﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace ALinq.Dynamic
{
    class QueryExpressionReducer : ExpressionVisitor
    {
        #region Class PropertyNamesFetch
        class PropertyNamesFetch : ExpressionVisitor
        {
            private List<MemberExpression> propertyNames;

            public PropertyNamesFetch()
            {
                propertyNames = new List<MemberExpression>();
            }

            public MemberExpression[] Execute(Expression node)
            {
                base.Visit(node);
                return propertyNames.ToArray();
            }

            protected override Expression VisitMember(MemberExpression node)
            {
                propertyNames.Add(node);
                return base.VisitMember(node);
            }

            //public IEnumerable<string> PropertyNames { get; private set; }
        }
        #endregion

        class Replacer : ExpressionVisitor
        {
            private Expression replacement;
            private Expression old;

            private Replacer(Expression old, Expression replacement)
            {
                this.old = old;
                this.replacement = replacement;
            }

            public static Expression Execute(Expression source, Expression old, Expression replacement)
            {
                return new Replacer(old, replacement).Visit(source);
            }

            public override Expression Visit(Expression node)
            {
                if (node == old)
                    return this.replacement;

                return base.Visit(node);
            }
        }

        class SelectionReducer : ExpressionVisitor
        {
            private static MemberExpression[] keepProperties;
            //private string[] KeepProperties { get; set; }

            public static Expression Reduece(Expression source, Expression selection, Expression keeps)
            {
                var reducer = new SelectionReducer();
                keepProperties = new PropertyNamesFetch().Execute(keeps);
                var replacement = reducer.Visit(selection);
                var expr = Replacer.Execute(source, selection, replacement);
                return expr;
            }

            protected override Expression VisitLambda<T>(Expression<T> node)
            {
                if (node.Body.NodeType == ExpressionType.New)
                {
                    var dymanicPropertis = keepProperties.Select(o => new DynamicProperty(o.Member.Name, o.Type));
                    var dynamicType = ClassFactory.Instance.GetDynamicClass(dymanicPropertis, typeof(DynamicObject));
                    var properties = dynamicType.GetProperties();

                    var p = Expression.Parameter(dynamicType);

                    var bindings = new MemberBinding[properties.Length];
                    for (var i = 0; i < bindings.Length; i++)
                    {
                        var ma = Expression.MakeMemberAccess(p, properties[i]);
                        bindings[i] = Expression.Bind(properties[i], ma);
                    }

                    var body = Expression.MemberInit(Expression.New(dynamicType), bindings);

                    var lambda = Expression.Lambda(body, node.Parameters);
                    return lambda;
                }
                return base.VisitLambda<T>(node);
            }

            protected override Expression VisitNew(NewExpression node)
            {
                //ClassFactory.Instance.GetDynamicClass()
                var dymanicPropertis = keepProperties.Select(o => new DynamicProperty(o.Member.Name, o.Type));
                var dynamicType = ClassFactory.Instance.GetDynamicClass(dymanicPropertis, typeof(DynamicObject));
                var properties = dynamicType.GetProperties();

                var p = Expression.Parameter(dynamicType);

                var bindings = new MemberBinding[properties.Length];
                for (var i = 0; i < bindings.Length; i++)
                {
                    var ma = Expression.MakeMemberAccess(p, properties[i]);
                    bindings[i] = Expression.Bind(properties[i], ma);
                }

                var expr = Expression.MemberInit(Expression.New(dynamicType), bindings);
                return expr;
            }

            protected override Expression VisitMemberInit(MemberInitExpression node)
            {
                return base.VisitMemberInit(node);
            }
        }

        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            switch (node.Method.Name)
            {
                case "Select":
                    ReduceExpression(node);
                    break;
            }
            return base.VisitMethodCall(node);
        }

        Expression ReduceExpression(MethodCallExpression select)
        {
            Debug.Assert(select.Method.Name == "Select");
            Debug.Assert(select.Arguments.Count == 2);
            var inner = select.Arguments[0];

            if (inner.NodeType == ExpressionType.Call &&
               ((MethodCallExpression)inner).Method.Name == "Select")
            {
                //var innerSelection = ((MethodCallExpression)inner).Arguments[1];
                inner = ReduceExpression((MethodCallExpression)inner);
                //var outerSelection = select.Arguments[1];
            }
            else
            {
                return select;
            }

            var outerSelection = select.Arguments[1];
            var innerSelection = ((MethodCallExpression)inner).Arguments[1];
            var expr = SelectionReducer.Reduece(inner, innerSelection, outerSelection);
            return inner;
        }
    }
}
